<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Markdown转思维导图</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
    <style>
      body {
        font-family: 'Arial', sans-serif;
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
        text-align: center;
        background-color: #f9f9f9;
      }
      h1 {
        color: #333;
        margin-bottom: 30px;
      }
      .upload-container {
        margin: 30px 0;
        padding: 20px;
        border: 2px dashed #ccc;
        border-radius: 10px;
        background-color: white;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
      }
      #fileInput {
        margin: 15px 0;
      }
      button {
        background-color: #4caf50;
        color: white;
        padding: 10px 20px;
        border: none;
        border-radius: 5px;
        cursor: pointer;
        font-size: 16px;
        transition: background-color 0.3s;
      }
      button:hover {
        background-color: #45a049;
      }
      #resultContainer {
        margin-top: 30px;
      }
      #mindmap-container {
        margin-top: 30px;
        border: 1px solid #ddd;
        padding: 20px;
        min-height: 500px;
        overflow: auto;
        background-color: white;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
        border-radius: 8px;
        position: relative;
      }
      .download-btn {
        background-color: #2196f3;
        margin-top: 15px;
        margin-right: 10px;
      }
      .download-btn:hover {
        background-color: #0b7dda;
      }
      .zoom-controls {
        position: absolute;
        top: 10px;
        right: 10px;
        z-index: 100;
      }
      .zoom-btn {
        width: 30px;
        height: 30px;
        padding: 0;
        margin-left: 5px;
        font-size: 16px;
        line-height: 30px;
      }
      .node-rect {
        stroke: #333;
        stroke-width: 1.5px;
        rx: 8px;
        ry: 8px;
        box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.15);
      }
      .node-level-0 {
        fill: #ff9e9e;
      }
      .node-level-1 {
        fill: #99ccff;
      }
      .node-level-2 {
        fill: #a5d6a7;
      }
      .node-level-3 {
        fill: #ffd54f;
      }
      .node-level-4 {
        fill: #ce93d8;
      }
      .node-highlight {
        fill: #ffab91;
        stroke: #333;
        stroke-width: 1.5px;
        rx: 8px;
        ry: 8px;
        box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.15);
      }
      .node-code {
        fill: #e0e0e0;
        stroke: #333;
        stroke-width: 1.5px;
        rx: 8px;
        ry: 8px;
        box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.15);
      }
      .node-math {
        fill: #b3e5fc;
        stroke: #333;
        stroke-width: 1.5px;
        rx: 8px;
        ry: 8px;
        box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.15);
      }
      .node-table {
        fill: #fff59d;
        stroke: #333;
        stroke-width: 1.5px;
        rx: 8px;
        ry: 8px;
        box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.15);
      }
      .link {
        fill: none;
        stroke: #555;
        stroke-width: 1.5px;
        stroke-opacity: 0.7;
      }
      .node-label {
        font-size: 12px;
        text-anchor: middle;
        pointer-events: none;
        fill: #333;
        font-weight: 500;
      }
      /* 添加箭头样式 */
      .style-controls {
        margin-bottom: 15px;
      }

      .arrowhead {
        fill: #555;
      }
      .container-row {
        display: flex;
        justify-content: space-between;
        max-width: 1000px;
        margin: 0 auto;
      }
      .half-width {
        width: 48%;
        margin: 20px 0;
      }
    </style>
  </head>
  <body>
    <h1>Markdown转思维导图</h1>

    <div class="container-row">
      <div class="upload-container half-width">
        <h3>上传Markdown文件</h3>
        <input type="file" id="fileInput" multiple />
        <br />
        <button id="convertBtn">转换为思维导图</button>
      </div>
      <div class="upload-container half-width">
        <h3>配置</h3>
         <div class="level-controls">
            <label for="levelSelector">选择级别: </label>
            <select id="levelSelector">
              <option value="all">所有级别</option>
              <option value="1">1级</option>
              <option value="2">2级</option>
              <option value="3">3级</option>
              <option value="4">4级</option>
            </select>
          </div>
          <div class="style-controls">
              <button id="downloadSvgBtn" class="download-btn" style="display: none">
                  下载SVG
              </button>
              <button id="downloadPngBtn" class="download-btn" style="display: none">
                  下载PNG
              </button>
          </div>
        
        
      </div>
    </div>

    <div id="resultContainer">
      <h3>思维导图结果</h3>
      <div
        class="controls-container"
        style="
          display: flex;
          justify-content: center;
          gap: 20px;
          margin-bottom: 15px;
        "
      >
        </div>
       
      <div id="mindmap-container" style="position: relative">
        <p>转换后的思维导图将显示在这里</p>
        <div class="zoom-controls">
          <button id="zoomIn" class="zoom-btn">+</button>
          <button id="zoomOut" class="zoom-btn">-</button>
          <button id="zoomReset" class="zoom-btn">⟲</button>
        </div>
      </div>
      
    </div>

    <script>
            let mindmapData = null;
            // 2. 处理CSS中的外部资源 - 使用单独的函数
            function processExternalCss(svg) {
                console.log('processExternalCss function called');
                const cssStyleElements = svg.querySelectorAll('style');
                cssStyleElements.forEach(style => {
                    if (style.textContent.includes('url(')) {
                        hasExternalResources = true;
                        console.warn('SVG contains external CSS resources');
                        // 直接移除所有包含url()的样式规则，避免正则表达式复杂性
                        const cleanCss = style.textContent.split('}')
                            .map(rule => {
                                if (rule.includes('url(')) {
                                    // 对于包含url()的规则，尝试保留选择器但移除有问题的属性
                                    return rule.split('{')[0] + '{}';
                                }
                                return rule;
                            })
                            .join('}');
                        style.textContent = cleanCss;
                    }
                });
                return svg;
            }

             // 解决Canvas污染问题: 检查并设置所有外部资源的crossOrigin属性
                function fixExternalResources(svg) {
                      console.log('fixExternalResources function called');
                    // 处理image元素
                    const images = svg.querySelectorAll('image');
                    images.forEach(img => {
                        const src = img.getAttribute('href') || img.getAttribute('xlink:href');
                        if (src && !src.startsWith('data:')) {
                            // 对于外部图片资源，添加crossOrigin属性
                            img.setAttribute('crossOrigin', 'anonymous');
                            // 如果是HTTP链接，转换为HTTPS
                            if (src.startsWith('http://')) {
                                img.setAttribute('href', src.replace('http://', 'https://'));
                                if (img.hasAttribute('xlink:href')) {
                                    img.removeAttribute('xlink:href');
                                }
                            }
                        }
                    });
                    return svg;
                }
       // 生成带时间戳的文件名
            function generateTimestampedFilename(baseName, extension) {
                // 生成格式为20250803122311的时间戳
                const date = new Date();
                const year = date.getFullYear();
                const month = String(date.getMonth() + 1).padStart(2, '0');
                const day = String(date.getDate()).padStart(2, '0');
                const hours = String(date.getHours()).padStart(2, '0');
                const minutes = String(date.getMinutes()).padStart(2, '0');
                const seconds = String(date.getSeconds()).padStart(2, '0');
                const timestamp = `${year}${month}${day}${hours}${minutes}${seconds}`;
                return `${baseName}_${timestamp}.${extension}`;
            }
            document.getElementById('convertBtn').addEventListener('click', function() {
                const fileInput = document.getElementById('fileInput');
                const file = fileInput.files[0];

                if (!file) {
                    alert('请先选择一个Markdown文件');
                    return;
                }

                const formData = new FormData();
                formData.append('file', file);

                fetch('/api/markdown/upload', {
                    method: 'POST',
                    body: formData
                })
                .then(response => {
                    if (!response.ok) {
                        throw new Error('转换失败');
                    }
                    return response.json();
                })
                .then(data => {
                    mindmapData = data;
                    renderMindMap(data);
                    document.getElementById('downloadSvgBtn').style.display = 'inline-block';
                    document.getElementById('downloadPngBtn').style.display = 'inline-block';
                })
                .catch(error => {
                    console.error('Error:', error);
                    alert('转换过程中发生错误');
                });
            });

            // 初始化级别选择器事件
            document.addEventListener('DOMContentLoaded', function() {
                // 级别选择器
                const levelSelector = document.getElementById('levelSelector');
                if (levelSelector) {
                    levelSelector.addEventListener('change', function() {
                        if (mindmapData) {
                            renderMindMap(mindmapData);
                        }
                    });
                }
            });
                // 渲染数学公式
                    function renderMathFormulas() {
                        document.querySelectorAll('.math-node').forEach(el => {
                            // 检查KaTeX是否已成功加载
                            if (katexLoaded && window.katex) {
                                try {
                                    katex.render(el.textContent, el, {
                                        throwOnError: false
                                    });
                                } catch (error) {
                                    console.error('Error rendering math formula:', error);
                                    el.textContent = 'Error rendering formula';
                                }
                            } else if (!katexLoaded) {
                                // 如果KaTeX尚未加载，显示等待消息
                                el.textContent = 'Loading formula...';
                                // 尝试稍后再次渲染
                                setTimeout(renderMathFormulas, 100);
                            } else {
                                // 如果KaTeX加载失败，显示原始内容
                                el.textContent = el.textContent;
                            }
                        });
                    }    

                // 加载KaTeX用于数学公式渲染
            function loadKaTeX() {
                return new Promise((resolve, reject) => {
                    const link = document.createElement('link');
                    link.rel = 'stylesheet';
                    link.href = 'https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css';
                    document.head.appendChild(link);

                    const script = document.createElement('script');
                    script.src = 'https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js';
                    script.onload = () => resolve();
                    script.onerror = reject;
                    document.head.appendChild(script);
                });
            }
                // 缩放控制按钮事件
                function setupZoomControls(svg, zoom) {
                    const zoomInBtn = document.getElementById('zoomIn');
                    const zoomOutBtn = document.getElementById('zoomOut');
                    const zoomResetBtn = document.getElementById('zoomReset');

                    if (zoomInBtn) {
                        zoomInBtn.onclick = function() {
                            svg.transition().call(zoom.scaleBy, 1.2);
                        };
                    }

                    if (zoomOutBtn) {
                        zoomOutBtn.onclick = function() {
                            svg.transition().call(zoom.scaleBy, 0.8);
                        };
                    }

                    if (zoomResetBtn) {
                        zoomResetBtn.onclick = function() {
                            svg.transition().call(zoom.transform, d3.zoomIdentity);
                        };
                    }
                }
            // 优化的文本分割算法，更好地处理中文和长单词          
            function renderMindMap(data) {
                // 获取选中的最大级别
                const levelSelector = document.getElementById('levelSelector');
                let maxLevel = 100; // 设置一个足够大的值，确保显示所有级别
                if (levelSelector && levelSelector.value !== 'all') {
                    maxLevel = parseInt(levelSelector.value);
                    console.log('Selected level:', maxLevel);
                }

                // 过滤数据，只保留指定级别以内的节点
                function filterDataByLevel(node, maxLevel) {
                    console.log('node.depth:', node.depth);
                    
                    // 递归过滤子节点
                    if (node.children) {
                        node.children = node.children
                            .map(child => {
                                // 只删除深度超过maxLevel的子节点
                                if (child.depth > maxLevel) {
                                    return null;
                                }
                                // 递归处理孙子节点
                                return filterDataByLevel(child, maxLevel);
                            })
                            .filter(child => child !== null); // 过滤掉被删除的节点
                    }

                    return node; // 始终返回当前节点
                }

                // 创建数据的深拷贝并应用过滤
                let filteredData = JSON.parse(JSON.stringify(data));
                console.log('maxLevel:', maxLevel);

                filteredData = filterDataByLevel(filteredData, maxLevel);
                console.log('Filtered data:', filteredData);
                // 如果根节点被过滤掉，创建一个空节点
                if (!filteredData) {
                    filteredData = { id: 'root', text: 'No nodes to display', depth: 0 };
                }
                const container = document.getElementById('mindmap-container');
                // 先隐藏提示文本，而不是清空整个容器
                const placeholder = container.querySelector('p');
                if (placeholder) {
                    placeholder.style.display = 'none'

                };
                // 清除之前的SVG，但保留容器和缩放控件
                const existingSvg = container.querySelector('svg');
                if (existingSvg) {
                    existingSvg.remove()
                };

                const width = container.clientWidth - 40;
                const height = Math.max(500, container.clientHeight - 40);

                const svg = d3.select('#mindmap-container')
                    .append('svg')
                    .attr('width', '100%')
                    .attr('height', height)
                    .attr('viewBox', `0 0 ${width} ${height}`)
                    .attr('preserveAspectRatio', 'xMidYMid meet');

                // 创建一个g元素来容纳所有内容，用于缩放
                const g = svg.append('g');

                // 创建缩放行为
                const zoom = d3.zoom()
                    .scaleExtent([0.5, 3])
                    .on('zoom', function(event) {
                        g.attr('transform', event.transform);
                    });

                svg.call(zoom);
                setupZoomControls(svg, zoom);

                // 转换数据为层次结构
                const root = d3.hierarchy(filteredData, d => d.children);
                root.x0 = 50;
                root.y0 = width / 2;

                // 树形布局 - 改为左右结构，优化节点间距防止重叠
                // 动态计算布局大小，确保有足够空间
                const calculateLayoutSize = () => {
                    // 根据节点数量和层级动态调整宽度
                    const nodeCount = root.descendants().length;
                    const maxDepth = d3.max(root.descendants(), d => d.depth) || 1;
                    const dynamicWidth = Math.max(width, nodeCount * 40 + maxDepth * 100);
                    return [height - 100, dynamicWidth];
                };

                const treeLayout = d3.tree()
                    .size(calculateLayoutSize())
                    .separation((a, b) => {
                        // 增加同一父节点的子节点间距
                        const sameParent = a.parent === b.parent;
                        // 根据深度调整间距，深度越大间距越小
                        const depthFactor = 1 / Math.max(a.depth, 1);
                        // 增加基础间距值，并根据节点类型调整
                        // 检测节点类型
                        let aType = 'normal';
                        let bType = 'normal';
                        if (a.data.name && (a.data.name.includes('```'))) aType = 'code';
                        if (a.data.name && (a.data.name.includes('|'))) aType = 'table';
                        if (b.data.name && (b.data.name.includes('```'))) bType = 'code';
                        if (b.data.name && (b.data.name.includes('|'))) bType = 'table';

                        // 增加所有节点类型的基础间距，减少重叠
                        const baseSpacing = (aType === 'code' || bType === 'code') ? 7 :
                                          (aType === 'table' || bType === 'table') ? 6 : 4;
                        return sameParent ? baseSpacing * depthFactor : (baseSpacing + 2) * depthFactor;
                    });

                // 计算节点位置
                const nodes = treeLayout(root).descendants();
                // 过滤空节点，但保留根节点（即使为空）以便处理连接线
                const nonEmptyNodes = nodes.filter(d => d.depth === 0 || (d.data.name && d.data.name.trim() !== ''));
                
                // 过滤连接线
                const links = treeLayout(root).links()
                    .filter(link => {
                        // 保留根节点到子节点的连接线（即使根节点为空）
                        if (link.source.depth === 0 && link.source.data.name.trim() === '') {
                            // 检查第一层子节点数量
                            const firstLevelChildren = root.children || [];
                            // 只有当第一层有多个子节点时，才保留根节点到子节点的连接线
                            return firstLevelChildren.length > 1 && link.target.data.name && link.target.data.name.trim() !== '';
                        }
                        // 其他连接线需要两端都是非空节点
                        return link.source.data.name && link.source.data.name.trim() !== ''
                            && link.target.data.name && link.target.data.name.trim() !== '';
                    });

                // 使用默认样式设置
                const linkColor = '#555';
                const nodeFill = '#fff';
                const nodeStroke = '#555';
                const textColor = '#333';
                const arrowColor = '#555';

                // 定义箭头标记
                svg.append('defs').append('marker')
                    .attr('id', 'arrowhead')
                    .attr('viewBox', '0 -5 10 10')
                    .attr('refX', 15)
                    .attr('refY', 0)
                    .attr('markerWidth', 6)
                    .attr('markerHeight', 6)
                    .attr('orient', 'auto')
                    .append('path')
                    .attr('d', 'M 0,-5 L 10,0 L 0,5')
                    .attr('class', 'arrowhead')
                    .attr('fill', arrowColor);

                // 绘制连接线
                g.selectAll('.link')
                    .data(links)
                    .enter()
                    .append('path')
                    .attr('class', 'link')
                    .attr('d', d => {
                        // 交换x和y以实现左右布局
                        return `M ${d.source.y} ${d.source.x} C ${(d.source.y + d.target.y) / 2} ${d.source.x}, ${(d.source.y + d.target.y) / 2} ${d.target.x}, ${d.target.y} ${d.target.x}`;
                    })
                    .attr('stroke', linkColor)
                    .attr('fill', 'none')
                    .attr('stroke-width', 1.5)
                    .attr('marker-end', 'url(#arrowhead)'); // 添加箭头

                // 更新连接线颜色
                g.selectAll('.link').attr('stroke', linkColor);

                // 更新箭头标记颜色
                svg.selectAll('#arrowhead path').attr('fill', arrowColor);

                // 绘制节点
                const node = g.selectAll('.node')
                    .data(nodes)
                    .enter()
                    .append('g')
                    .attr('transform', d => `translate(${d.y},${d.x})`);

                // 处理不同类型的节点
                node.each(function(d) {
                    const g = d3.select(this);
                    let nodeType = 'normal';
                    let nodeContent = d.data.name;
                    let nodeWidth = 0;
                    let nodeHeight = 30;
                    const maxWidth = 180; // 所有节点的最大宽度

                    // 处理空节点和根节点渲染逻辑
                    if (!d.data.name || d.data.name.trim() === '') {
                        // 根节点特殊处理
                        if (d.depth === 0) {
                            // 检查第一层子节点数量
                            const firstLevelChildren = root.children || [];
                            // 如果第一层有多个子节点，则渲染根节点为圆点
                            if (firstLevelChildren.length > 1) {
                                g.append('circle')
                                    .attr('class', 'node-dot')
                                    .attr('r', 5)
                                    .attr('fill', '#333');
                            }
                            // 如果只有一个子节点，则不渲染根节点
                        }
                        return;
                    }

                    // 检测节点类型
                    if (d.data.name.includes('**') || d.data.name.includes('__') ||
                        d.data.name.includes('*') || d.data.name.includes('_') ||
                        d.data.name.includes('`')) {
                        nodeType = 'highlight';
                    } else if (d.data.name.includes('$$')) {
                        nodeType = 'math';
                    } else if (d.data.name.includes('|')) {
                        nodeType = 'table';
                    } else if (d.data.name.includes('```')) {
                        nodeType = 'code';
                    }

                    // 处理不同类型的节点
                    if (nodeType === 'code') {
                        // 代码块节点
                        const code = d.data.name.replace(/```/g, '').trim();
                        // 限制代码块最大宽度
                        nodeWidth = Math.min(Math.max(code.length * 8, 100) + 16, maxWidth);
                        nodeHeight = code.split('\n').length * 18 + 10;

                        g.append('rect')
                            .attr('class', 'node-code')
                            .attr('width', nodeWidth)
                            .attr('height', nodeHeight)
                            .attr('x', -nodeWidth / 2)
                            .attr('y', -nodeHeight / 2);

                        g.append('foreignObject')
                            .attr('width', nodeWidth - 10)
                            .attr('height', nodeHeight - 10)
                            .attr('x', -nodeWidth / 2 + 5)
                            .attr('y', -nodeHeight / 2 + 5)
                            .append('xhtml:div')
                            .style('font-family', 'monospace')
                            .style('font-size', '12px')
                            .text(code);
                    } else if (nodeType === 'math') {
                        // 数学公式节点
                        const math = d.data.name.replace(/\$\$/g, '').trim();
                        // 限制数学公式节点最大宽度
                        nodeWidth = Math.min(math.length * 10 + 20, maxWidth);
                        nodeHeight = 40;

                        g.append('rect')
                            .attr('class', 'node-math')
                            .attr('width', nodeWidth)
                            .attr('height', nodeHeight)
                            .attr('x', -nodeWidth / 2)
                            .attr('y', -nodeHeight / 2);

                        g.append('foreignObject')
                            .attr('width', nodeWidth)
                            .attr('height', nodeHeight)
                            .attr('x', -nodeWidth / 2)
                            .attr('y', -nodeHeight / 2)
                            .append('xhtml:div')
                            .attr('class', 'math-node')
                            .text(math);
                    } else if (nodeType === 'table') {
                        // 表格节点
                        const tableData = d.data.name.split('|').filter(s => s.trim() !== '');
                        const headers = tableData.slice(0, 2);
                        const rows = [];
                        for (let i = 2; i < tableData.length; i += 2) {
                            rows.push([tableData[i], tableData[i+1]]);
                        }

                        // 限制表格节点最大宽度
                        nodeWidth = Math.min(150, maxWidth);
                        nodeHeight = 25 + rows.length * 20;

                        g.append('rect')
                            .attr('class', 'node-table')
                            .attr('width', nodeWidth)
                            .attr('height', nodeHeight)
                            .attr('x', -nodeWidth / 2)
                            .attr('y', -nodeHeight / 2);

                        const table = g.append('foreignObject')
                            .attr('width', nodeWidth - 10)
                            .attr('height', nodeHeight - 10)
                            .attr('x', -nodeWidth / 2 + 5)
                            .attr('y', -nodeHeight / 2 + 5)
                            .append('xhtml:table');

                        // 创建表头
                        const thead = table.append('xhtml:thead');
                        const headerRow = thead.append('xhtml:tr');
                        headers.forEach(header => {
                            headerRow.append('xhtml:th').text(header.trim());
                        });

                        // 创建表体
                        const tbody = table.append('xhtml:tbody');
                        rows.forEach(row => {
                            const tr = tbody.append('xhtml:tr');
                            row.forEach(cell => {
                                tr.append('xhtml:td').text(cell.trim());
                            });
                        });
                    } else if (nodeType === 'highlight') {
                        // 高亮文本节点
                        // 处理粗体、斜体等格式
                        let processedText = d.data.name
                            .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
                            .replace(/__(.*?)__/g, '<strong>$1</strong>')
                            .replace(/\*(.*?)\*/g, '<em>$1</em>')
                            .replace(/_(.*?)_/g, '<em>$1</em>')
                            .replace(/`(.*?)`/g, '<code>$1</code>');

                        // 限制高亮节点最大宽度
                        nodeWidth = Math.min(d.data.name.length * 12 + 16, maxWidth);
                        nodeHeight = 30;

                        g.append('rect')
                            .attr('class', 'node-highlight')
                            .attr('width', nodeWidth)
                            .attr('height', nodeHeight)
                            .attr('x', -nodeWidth / 2)
                            .attr('y', -nodeHeight / 2);

                        g.append('foreignObject')
                            .attr('width', nodeWidth - 10)
                            .attr('height', nodeHeight - 10)
                            .attr('x', -nodeWidth / 2 + 5)
                            .attr('y', -nodeHeight / 2 + 5)
                            .append('xhtml:div')
                            .html(processedText);
                    } else {
                        // 普通节点 - 根据层级应用不同颜色
                        const maxWidth = 180; // 设置最大宽度，超过则换行
                        const lineHeight = 20; // 行高
                        const text = d.data.name;
                        const lines = [];
                        let currentLine = '';
                        // 分割文本为多行
                        const words = text.split(' ');
                        for (const word of words) {
                            // 检查添加当前单词后是否超过最大宽度
                            const testLine = currentLine ? currentLine + ' ' + word : word;
                            const testWidth = calculateTextWidth(testLine);

                            if (testWidth > maxWidth - 20) { // 减去边距
                                if (currentLine) {
                                    lines.push(currentLine);
                                    currentLine = word;
                                } else {
                                    // 如果单个单词就超过最大宽度，尝试按字符拆分
                                    if (calculateTextWidth(word) > maxWidth - 20) {
                                        // 对于超长单词或无空格文本，按字符拆分
                                        let tempLine = '';
                                        for (let i = 0; i < word.length; i++) {
                                            const tempTestLine = tempLine + word[i];
                                            if (calculateTextWidth(tempTestLine) > maxWidth - 20) {
                                                lines.push(tempLine);
                                                tempLine = word[i];
                                            } else {
                                                tempLine = tempTestLine;
                                            }
                                        }
                                        if (tempLine) {
                                            currentLine = tempLine;
                                        }
                                    } else {
                                        lines.push(word);
                                        currentLine = '';
                                    }
                                }
                            } else {
                                currentLine = testLine;
                            }
                        }
                        if (currentLine) {
                            lines.push(currentLine);
                        }

                        // 计算节点宽度和高度 - 确保宽度足够容纳最长行
                        const longestLineWidth = Math.max(...lines.map(line => calculateTextWidth(line))) + 16;
                        nodeWidth = Math.min(longestLineWidth, maxWidth);
                        nodeHeight = lines.length * lineHeight + 10;

                         const levelClass = `node-level-${Math.min(d.depth, maxLevel)}`;

                        // 设置节点填充色 - 使用CSS类定义的颜色
                        const fillColor = null;

                        g.append('rect')
                            .attr('class', `node-rect ${levelClass}`)
                            .attr('width', nodeWidth)
                            .attr('height', nodeHeight)
                            .attr('x', -nodeWidth / 2)
                            .attr('y', -nodeHeight / 2)
                            .attr('stroke', nodeStroke)
                            .attr('rx', 5)
                            .attr('ry', 5)
                            .attr('fill', fillColor);

                        // 多行文本渲染 - 确保文本居中并在节点框内
                        const textElement = g.append('text')
                            .attr('class', 'node-label')
                            .attr('text-anchor', 'middle')
                            .attr('fill', textColor)
                            // 设置文本基线，确保垂直居中
                            .attr('dominant-baseline', 'central')
                            // 初始位置设置为节点垂直中心
                            .attr('y', 0);

                        // 计算第一行的偏移量
                        const firstLineOffset = -(lines.length - 1) * lineHeight / 2;

                        lines.forEach((line, i) => {
                            textElement.append('tspan')
                                .attr('x', 0)
                                .attr('dy', i === 0 ? firstLineOffset + 'px' : '1.2em')
                                .text(line);
                        });

                        // 确保节点宽度至少能容纳所有文本行
                        if (nodeWidth < longestLineWidth) {
                            nodeWidth = Math.min(longestLineWidth, maxWidth);
                            // 更新节点矩形宽度
                            g.select('rect.node-rect')
                                .attr('width', nodeWidth)
                                .attr('x', -nodeWidth / 2);
                        }
                // 初始调用渲染函数
                renderMathFormulas();
            };


            // SVG下载功能 - 改进版，确保样式正确内联
            document.getElementById('downloadSvgBtn').onclick = function() {
                if (!mindmapData) {
                    alert('没有可下载的思维导图');
                    return;
                }

                const container = document.getElementById('mindmap-container');
                const svg = container.querySelector('svg');
                if (!svg) {
                    alert('没有可下载的SVG');
                    return;
                }

                // 克隆SVG节点，以便我们可以修改它而不影响原始显示
                let clonedSvg = svg.cloneNode(true);

                // 将所有外部样式内联到SVG中
                const styleSheets = document.styleSheets;
                const styleElement = document.createElementNS('http://www.w3.org/2000/svg', 'style');
                let cssText = '';

                // 确保获取所有样式表中的规则
                for (let i = 0; i < styleSheets.length; i++) {
                    const styleSheet = styleSheets[i];
                    try {
                        if (styleSheet.cssRules) {
                            for (let j = 0; j < styleSheet.cssRules.length; j++) {
                                cssText += styleSheet.cssRules[j].cssText + '\n';
                            }
                        }
                    } catch (e) {
                        console.warn('无法访问样式表:', e);
                    }
                }

                // 手动添加节点样式，确保颜色被正确应用
                const nodeStyles = `
                    .node-rect { fill: white; stroke-width: 2; rx: 5; ry: 5; }
                    .node-level-0 { stroke: #3498db; fill: #ebf5fa; }
                    .node-level-1 { stroke: #2ecc71; fill: #eafaf1; }
                    .node-level-2 { stroke: #f1c40f; fill: #fef9e7; }
                    .node-level-3 { stroke: #e74c3c; fill: #fdedeb; }
                    .node-level-4 { stroke: #9b59b6; fill: #f5eef8; }
                    .node-label { font-family: Arial, sans-serif; font-size: 14px; fill: #333; }
                    .connection-line { fill: none; stroke: #999; stroke-width: 2; }
                `;
                cssText += nodeStyles;

                styleElement.textContent = cssText;
                clonedSvg.insertBefore(styleElement, clonedSvg.firstChild);

                // 处理外部资源
                clonedSvg = processExternalCss(clonedSvg);

                // 移除所有链接元素
                const links = clonedSvg.querySelectorAll('link');
                links.forEach(link => link.parentNode.removeChild(link));

                // 获取处理后的SVG代码
                const svgData = new XMLSerializer().serializeToString(clonedSvg);

                // 创建Blob对象
                const blob = new Blob([svgData], {type: 'image/svg+xml;charset=utf-8'});
                const url = URL.createObjectURL(blob);

                // 创建下载链接
                const a = document.createElement('a');
                a.href = url;
                a.download = generateTimestampedFilename('markdow2Mindmap-svg', 'svg');
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
            };

            // PNG下载功能改进 - 解决图片不全、颜色不一致和清晰度问题
            document.getElementById('downloadPngBtn').onclick = function() {
                if (!mindmapData) {
                    alert('没有可下载的思维导图');
                    return;
                }

                const container = document.getElementById('mindmap-container');
                const svg = container.querySelector('svg');
                if (!svg) {
                    alert('没有可下载的SVG');
                    return;
                }

                // 获取SVG的viewBox属性以确定实际内容大小
                const viewBox = svg.getAttribute('viewBox');
                let svgWidth, svgHeight;

                if (viewBox) {
                    // 如果有viewBox，使用viewBox的宽度和高度
                    const [x, y, width, height] = viewBox.split(' ').map(Number);
                    svgWidth = width;
                    svgHeight = height;
                } else {
                    // 否则使用SVG元素的属性宽度和高度
                    svgWidth = parseInt(svg.getAttribute('width'));
                    svgHeight = parseInt(svg.getAttribute('height'));
                }

                // 考虑设备像素比
                const devicePixelRatio = window.devicePixelRatio || 1;

                // 创建一个临时画布
                const tempCanvas = document.createElement('canvas');
                const ctx = tempCanvas.getContext('2d');

                // 计算 backingStoreRatio 和 ratio
                const backingStoreRatio = ctx ? (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1) : 1;
                const ratio = devicePixelRatio / backingStoreRatio;

                // 设置画布大小，考虑设备像素比
                tempCanvas.width = svgWidth * ratio;
                tempCanvas.height = svgHeight * ratio;
                tempCanvas.style.width = svgWidth + 'px';
                tempCanvas.style.height = svgHeight + 'px';

                // 缩放上下文以考虑像素比
                ctx.scale(ratio, ratio);

                // 确保SVG中的样式被正确应用
                // 克隆SVG节点，以便我们可以修改它而不影响原始显示
                let clonedSvg = svg.cloneNode(true);

                // 将所有外部样式内联到SVG中
                const styleSheets = document.styleSheets;
                const styleElement = document.createElementNS('http://www.w3.org/2000/svg', 'style');
                let cssText = '';

                // 确保获取所有样式表中的规则
                for (let i = 0; i < styleSheets.length; i++) {
                    const styleSheet = styleSheets[i];
                    try {
                        if (styleSheet.cssRules) {
                            for (let j = 0; j < styleSheet.cssRules.length; j++) {
                                cssText += styleSheet.cssRules[j].cssText + '\n';
                            }
                        }
                    } catch (e) {
                        console.warn('无法访问样式表:', e);
                    }
                }

                // 手动添加节点样式，确保颜色被正确应用
                const nodeStyles = `
                    .node-rect { fill: white; stroke-width: 2; rx: 5; ry: 5; }
                    .node-level-0 { stroke: #3498db; fill: #ebf5fa; }
                    .node-level-1 { stroke: #2ecc71; fill: #eafaf1; }
                    .node-level-2 { stroke: #f1c40f; fill: #fef9e7; }
                    .node-level-3 { stroke: #e74c3c; fill: #fdedeb; }
                    .node-level-4 { stroke: #9b59b6; fill: #f5eef8; }
                    .node-label { font-family: Arial, sans-serif; font-size: 14px; fill: #333; }
                    .connection-line { fill: none; stroke: #999; stroke-width: 2; }
                `;
                cssText += nodeStyles;

                styleElement.textContent = cssText;
                clonedSvg.insertBefore(styleElement, clonedSvg.firstChild);



                // 将SVG转换为图像
                const img = new Image();
                // 关键：在设置src之前先设置crossOrigin属性
                img.crossOrigin = 'anonymous';

                // 增强版外部资源处理，防止Canvas污染
                let hasExternalResources = false;

                // 1. 处理图像元素
                const externalImages = clonedSvg.querySelectorAll('image[href], image[xlink\\:href]');
                externalImages.forEach(imgEl => {
                    const href = imgEl.getAttribute('href') || imgEl.getAttribute('xlink:href');
                    if (href && !href.startsWith('data:')) {
                        hasExternalResources = true;
                        console.warn('SVG contains external image resource:', href);

                        // 替换为占位符图像
                        const width = imgEl.getAttribute('width') || '100';
                        const height = imgEl.getAttribute('height') || '100';
                        const placeholderData = 'data:image/svg+xml;charset=utf-8,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22' + width + '%22 height=%22' + height + '%22%3E%3Crect width=%22100%25%22 height=%22100%25%22 fill=%22%23f0f0f0%22/%3E%3Ctext x=%2250%25%22 y=%2250%25%22 dominant-baseline=%22middle%22 text-anchor=%22middle%22 fill=%22%23999%22 font-size=%2212%22%3EImage%3C/text%3E%3C/svg%3E';

                        imgEl.setAttribute('href', placeholderData);
                        imgEl.removeAttribute('xlink:href');
                    }
                });


                clonedSvg = processExternalCss(clonedSvg);

                // 3. 移除所有链接元素
                const links = clonedSvg.querySelectorAll('link');
                links.forEach(link => link.parentNode.removeChild(link));

                if (hasExternalResources) {
                    console.warn('External resources in SVG have been replaced to prevent canvas tainting');
                }

                // 额外检查：确保SVG中没有其他可能导致污染的外部资源
                const styleElements = clonedSvg.querySelectorAll('style');
                styleElements.forEach(style => {
                    if (style.textContent.includes('url(')) {
                        console.warn('SVG contains external CSS resources which may taint the canvas');
                        // 可以选择移除包含外部资源的样式
                        // style.textContent = style.textContent.replace(/url\(['"]?[^'"]+['"]?\)/g, 'none');
                    }
                });

                // 应用修复
                let fixedSvg = fixExternalResources(clonedSvg);
                // 使用修复后的SVG生成数据
                const svgData = new XMLSerializer().serializeToString(fixedSvg);
                const blob = new Blob([svgData], {type: 'image/svg+xml;charset=utf-8'});
                const url = URL.createObjectURL(blob);

                // 使用Promise处理图像加载，确保顺序正确
                const loadImage = new Promise((resolve, reject) => {
                    img.onload = () => resolve(img);
                    img.onerror = (e) => reject(new Error('Failed to load SVG image: ' + e.type));
                    img.src = url;
                });

                loadImage.then(() => {
                    try {
                            // 确保画布大小与SVG实际大小匹配，并考虑设备像素比
                            let svgWidth, svgHeight;
                            try {
                                // 尝试获取SVG的width和height属性值
                                svgWidth = fixedSvg.width.baseVal.value;
                                svgHeight = fixedSvg.height.baseVal.value;

                                // 如果获取的值为0或NaN，使用BBox作为备选
                                if (!svgWidth || !svgHeight || isNaN(svgWidth) || isNaN(svgHeight)) {
                                    throw new Error('Invalid SVG dimensions');
                                }
                            } catch (error) {
                                console.warn('无法获取SVG尺寸，使用边界框作为备选:', error);
                                // 获取SVG内容的边界框
                                const bbox = fixedSvg.getBBox();
                                svgWidth = bbox.width;
                                svgHeight = bbox.height;

                                // 如果边界框也不可用，使用默认尺寸
                                if (!svgWidth || !svgHeight) {
                                    svgWidth = 800;
                                    svgHeight = 600;
                                    console.warn('使用默认尺寸:', svgWidth, 'x', svgHeight);
                                }
                            }

                            // 重新应用设备像素比缩放
                            const devicePixelRatio = window.devicePixelRatio || 1;
                            const backingStoreRatio = ctx ? (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1) : 1;
                            const ratio = devicePixelRatio / backingStoreRatio;

                            tempCanvas.width = svgWidth * ratio;
                            tempCanvas.height = svgHeight * ratio;
                            tempCanvas.style.width = svgWidth + 'px';
                            tempCanvas.style.height = svgHeight + 'px';

                            // 重新缩放上下文
                            ctx.scale(ratio, ratio);

                            // 确保画布背景颜色与原始SVG一致
                            const backgroundColor = getComputedStyle(container).backgroundColor;
                            ctx.fillStyle = backgroundColor;
                            ctx.fillRect(0, 0, svgWidth, svgHeight);

                            // 设置图像平滑质量
                            ctx.imageSmoothingEnabled = true;
                            ctx.imageSmoothingQuality = 'high';

                            // 计算画布中心位置
                            const canvasCenterX = tempCanvas.width / 2 / ratio;
                            const canvasCenterY = tempCanvas.height / 2 / ratio;
                            
                            // 计算图像中心位置
                            const imageCenterX = svgWidth / 2;
                            const imageCenterY = svgHeight / 2;
                            
                            // 计算左上角坐标使图像居中
                            const drawX = canvasCenterX - imageCenterX;
                            const drawY = canvasCenterY - imageCenterY;
                            
                            // 可选：添加最大尺寸限制，确保图片大小合适
                            const maxDimension = 1200; // 最大宽度和高度
                            let scale = 1;
                            if (svgWidth > maxDimension || svgHeight > maxDimension) {
                                scale = maxDimension / Math.max(svgWidth, svgHeight);
                            }
                            
                            // 在画布上绘制居中的SVG图像，应用高质量缩放
                            ctx.drawImage(img, drawX, drawY, svgWidth * scale, svgHeight * scale);

                        // 函数已在外部作用域定义
                        // 处理image元素
                        const images = fixedSvg.querySelectorAll('image');
                        images.forEach(img => {
                            const src = img.getAttribute('href') || img.getAttribute('xlink:href');
                            if (src && !src.startsWith('data:')) {
                                // 对于外部图片资源，添加crossOrigin属性
                                img.setAttribute('crossOrigin', 'anonymous');
                                // 确保URL使用https协议以避免混合内容问题
                                if (src.startsWith('http://')) {
                                    img.setAttribute('href', src.replace('http://', 'https://'));
                                    if (img.hasAttribute('xlink:href')) {
                                        img.setAttribute('xlink:href', src.replace('http://', 'https://'));
                                    }
                                }
                            }
                        });

                    } catch (error) {
                        console.error('Error processing SVG:', error);
                        alert('处理SVG时出错: ' + error.message);
                    }

                    // 创建下载链接
                    const a = document.createElement('a');
                    try {
                        // 使用高质量参数生成PNG
                        a.href = tempCanvas.toDataURL('image/png', 1.0);
                        a.download = generateTimestampedFilename('markdow2Mindmap-png', 'png');
                        document.body.appendChild(a);
                        a.click();
                        document.body.removeChild(a);
                    } catch (securityError) {
                            console.error('Canvas is tainted:', securityError);
                            // 画布被污染，尝试备选方案
                            alert('PNG下载失败: 由于安全限制，无法导出包含外部资源的图像。\n\n请尝试使用SVG格式下载，或确保所有图像资源都支持CORS。');
                            // 作为备选方案，我们可以提供SVG下载
                            const svgOption = confirm('是否尝试下载SVG格式？');
                            if (svgOption) {
                                const svgA = document.createElement('a');
                                svgA.href = url;
                                svgA.download = generateTimestampedFilename('mindmap', 'svg');
                                document.body.appendChild(svgA);
                                svgA.click();
                                document.body.removeChild(svgA);
                            }
                            URL.revokeObjectURL(url);
                        }
                    });

            }
        });
    }


            // 计算文本宽度函数
            function calculateTextWidth(text) {
                // 创建一个临时画布和上下文
                const canvas = document.createElement('canvas');
                const context = canvas.getContext('2d');
                // 设置与节点文本相同的字体样式
                context.font = '14px Arial';
                // 测量文本宽度
                const metrics = context.measureText(text);
                return metrics.width;
            }

            // 全局变量标记KaTeX是否已加载
            let katexLoaded = false;

            // 页面加载完成后加载KaTeX
            window.onload = async function() {
                console.log('Page loaded, checking for errors...');
                try {
                    await loadKaTeX();
                    katexLoaded = true;
                    console.log('KaTeX loaded successfully');
                } catch (error) {
                    console.error('Failed to load KaTeX:', error);
                    katexLoaded = false;
                }
            };
    </script>
  </body>
</html>
